// (c) Copyright slimCODE Software Inc. - www.slimcode.com
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace SlimCode.Utils
{
  internal class RijndaelCtrMode : IDisposable
  {
    public RijndaelCtrMode( byte[] key, int blockSize )
    {
      if( key == null )
        throw new ArgumentNullException( "key" );

      RijndaelManaged rijndael = new RijndaelManaged();

      rijndael.BlockSize = blockSize * 8;
      rijndael.Mode = CipherMode.ECB;

      m_rijndaelTransform = rijndael.CreateEncryptor( key, null );

      m_blockSize = blockSize;
      m_encryptionBlock = new byte[ m_blockSize ];
      m_nonce = new byte[ m_blockSize ];

      // We want the first encryption iteration to update the nonce.
      m_position = blockSize;
    }

    #region PUBLIC METHODS

    public void Encrypt( byte[] data, int offset, int count )
    {
      for( int i = 0; i < count; i++ )
      {
        if( m_position == m_blockSize )
        {
          UpdateNonce();
          m_position = 0;
        }

        data[ offset + i ] ^= m_encryptionBlock[ m_position++ ];
      }
    }

    #endregion

    #region PRIVATE METHODS

    private void UpdateNonce()
    {
      // Increment the nonce. No need to increment more than the
      // first 8 bytes (2^64).
      for( int i = 0; i < 8; i++ )
      {
        if( m_nonce[ i ] < 255 )
        {
          ++m_nonce[ i ];
          break;
        }

        m_nonce[ i ] = 0;
      }

      // encrypt the nonce to form next xor buffer
      m_rijndaelTransform.TransformBlock( m_nonce, 0, m_nonce.Length, m_encryptionBlock, 0 );
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
      if( m_rijndaelTransform != null )
        m_rijndaelTransform.Dispose();
    }

    #endregion

    private int m_blockSize = 16;
    private byte[] m_nonce; // = null
    private byte[] m_encryptionBlock; // = null
    private int m_position; // = 0

    private ICryptoTransform m_rijndaelTransform; // = null
  }
}
